/**
 * FileFind - ftp/shares indexing and search php engine
 * Module: agent-java
 * Description: Agent written in java language
 * Copyright (c) 2003-2010 Rage Steel, www.gt2.ru
 */
package ru.ffind.agent.dao;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.sql.DataSource;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import ru.ffind.agent.db.DbDirectory;
import ru.ffind.agent.db.DbFilename;
import ru.ffind.agent.db.DbHost;
import ru.ffind.common.model.Directory;
import ru.ffind.common.model.Entry;
import ru.ffind.agent.model.RootDirectory;
import ru.ffind.common.model.HostType;

/**
 * Implementation of data access object.
 *
 * @author Rage Steel
 */
public class DaoImpl implements Dao {
	
	private static final Logger LOGGER = Logger.getLogger(
			DaoImpl.class.getName());

	private final SimpleJdbcTemplate simpleJdbcTemplate;

    private final SimpleJdbcInsert simpleJdbcInsertHost;

	private final SimpleJdbcInsert simpleJdbcInsertDir;

	private final SimpleJdbcInsert simpleJdbcInsertFile;

	private final Date appDate = new Date();

    public DaoImpl(DataSource dataSource) {
        simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
        simpleJdbcInsertHost = new SimpleJdbcInsert(dataSource)
                .withTableName("ff_hosts").usingGeneratedKeyColumns("host_id");
		simpleJdbcInsertDir = new SimpleJdbcInsert(dataSource)
                .withTableName("ff_dirs").usingGeneratedKeyColumns("dir_id");
		simpleJdbcInsertFile = new SimpleJdbcInsert(dataSource)
                .withTableName("ff_files");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DbHost findOrCreateHost(String hostName, int ipAddress,
            HostType hostType, String hostOwner) {
		String sql = "SELECT `host_id`, `host_type`, `host_name`, `host_ip`, `lock_pid`, `lock_datetime`, `root_dir_id`, `crawl_last` " +
				"FROM `ff_hosts` " +
				"WHERE :host_name = host_name";
		Map<String, Object> args = Collections.singletonMap("host_name",
                        (Object)hostName);

		try {
			DbHost result = simpleJdbcTemplate.queryForObject(sql, DbHost.ROW_MAPPER, args);
			return result;
		} catch (EmptyResultDataAccessException e) {
			// Do nothing
		}

        // Create Host
        args = Maps.newHashMapWithExpectedSize(4);
        args.put("host_type", hostType.getId());
        args.put("host_name", hostName);
        args.put("host_ip", ipAddress);
        args.put("host_owner", hostOwner);
		args.put("date_add", appDate);
        args.put("host_opt", 0);
        args.put("host_status", 0);
        args.put("conn_error", 0);

		Short hostId = simpleJdbcInsertHost.executeAndReturnKey(args).shortValue();
//		System.out.println("Created host: " + hostId);

        DbHost result = new DbHost(hostId, (byte)hostType.getId(), hostName,
                null, null, null, null);

        // Create root directory
        Directory rootBase = RootDirectory.create(ImmutableList.<Entry>of());
        DbDirectory rootDir = createDir(hostId, null, rootBase);

        // Set created root directory id to host
        updateHostRootDirId(result, rootDir);

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean lockHost(DbHost host) {
		Integer lockPid = Integer.valueOf(0);
		Date lockDatetime = new Date();
		/*
UPDATE `ff_hosts` SET `lock_pid` = 0, `lock_datetime` = NOW()
				WHERE (1 = `host_id`) AND (`lock_pid` IS NULL) AND (`lock_datetime` IS NULL);
		 */
		String sql = "UPDATE `ff_hosts` SET `lock_pid` = :lock_pid, `lock_datetime` = :lock_datetime " +
				"WHERE (:host_id = `host_id`) AND (`lock_pid` IS NULL) AND (`lock_datetime` IS NULL)";

		// FIXME Add OR lock_datetime is more then one day old
		Map<String, Object> args = Maps.newHashMapWithExpectedSize(3);
		args.put("host_id", host.getHostId());
		args.put("lock_pid", lockPid);
		args.put("lock_datetime", lockDatetime);
		if (1 != simpleJdbcTemplate.update(sql, args)) {
			return false;
		}
		host.setLockPid(0);
		host.setLockDatetime(lockDatetime);
		return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean unlockHost(DbHost host) {
		String sql = "UPDATE `ff_hosts` SET `lock_pid` = NULL, `lock_datetime` = NULL " +
		"WHERE :host_id = `host_id` AND :lock_pid = `lock_pid` AND :lock_datetime = `lock_datetime`";

		Map<String, Object> args = Maps.newHashMapWithExpectedSize(3);
		args.put("host_id", host.getHostId());
		args.put("lock_pid", host.getLockPid());
		args.put("lock_datetime", host.getLockDatetime());
		if (1 != simpleJdbcTemplate.update(sql, args)) {
			return false;
		}
		host.setLockPid(null);
		host.setLockDatetime(null);
		return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DbDirectory queryDirectory(int dirId, DbDirectory placeHolder) {
		Map<String, Object> args = Collections.singletonMap("dir_id",
                        (Object)Integer.valueOf(dirId));

		DbDirectory result;
		String sqlDirBase = "SELECT `dir_id`, `par_dir_id`, `host_id`, `dir_name`, " +
			"`dir_flags`, `dir_link_path`, " +
			"`files_count`, `dirs_count`, `total_size`," +
			"`dir_datetime`, `app_date` " +
			"FROM `ff_dirs` ";

		if (null == placeHolder) {
			String sqlDir = sqlDirBase + "WHERE :dir_id = `dir_id`";

			result = simpleJdbcTemplate.queryForObject(sqlDir, DbDirectory.ROW_MAPPER, args);
		} else {
			result = placeHolder;
		}

		String sqlSubDirs = sqlDirBase + "WHERE :dir_id = `par_dir_id`";
		List<DbDirectory> subDirs = simpleJdbcTemplate.query(sqlSubDirs, DbDirectory.ROW_MAPPER, args);

		String sqlFiles = "SELECT `file_id`, `dir_id`, `host_id`, " +
				"`file_name`, `file_flags`, `file_size`, " +
				"`file_datetime`, `app_date` " +
				"FROM `ff_files` WHERE :dir_id = `dir_id`";
		List<DbFilename> dirFiles = simpleJdbcTemplate.query(sqlFiles, DbFilename.ROW_MAPPER, args);

		List<Entry> entries = Lists.newArrayList();
		entries.addAll(subDirs);
		entries.addAll(dirFiles);
		result.setEntries(ImmutableList.copyOf(entries));

		return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
   	public void updateHostRootDirId(DbHost host, DbDirectory rootDir) {
		String sql = "UPDATE `ff_hosts` SET `root_dir_id` = :root_dir_id " +
				"WHERE :host_id = `host_id`";
		Map<String, Object> args = ArgUtils.createHostArgs(host);
        Integer rootDirId = rootDir.getDirId();
		args.put("root_dir_id", rootDirId);

		simpleJdbcTemplate.update(sql, args);
        host.setRootDirId(rootDirId);
	}

    /**
     * {@inheritDoc}
     */
    @Override
	public DbDirectory createDir(Short hostId, DbDirectory parentDir,
            Directory base) {
		Map<String, Object> args = Maps.newHashMapWithExpectedSize(10);
		Integer parDirId = (null == parentDir) ? null : parentDir.getDirId();

		args.put("par_dir_id", parDirId);
		args.put("host_id", hostId);
		args.put("dir_name", base.getName());
		args.put("dir_flags", 0);
		args.put("dir_link_path", null);
		args.put("files_count", 0);
		args.put("dirs_count", 0);
		args.put("total_size", 0);
		args.put("dir_datetime", base.getDatetime());
		args.put("app_date", appDate);

		Integer dirId = simpleJdbcInsertDir.executeAndReturnKey(args).intValue();
//		System.out.println("Created directory: " + dirId);

		return new DbDirectory(dirId, parDirId, hostId, base.getName(),
				(byte)0, "",
				0, 0, 0L,
				base.getDatetime(), appDate);
	}

    /**
     * {@inheritDoc}
     */
    @Override
	public void updateDirStats(DbDirectory dir) {
		String sql = "UPDATE `ff_dirs` SET `files_count` = :files_count, `dirs_count` = :dirs_count, `total_size` = :total_size " +
				"WHERE :dir_id = `dir_id`";
		Map<String, Object> args = ArgUtils.createDirArgs(dir);
		args.put("files_count", dir.getFilesCount());
		args.put("dirs_count", dir.getDirsCount());
		args.put("total_size", dir.getTotalSize());

		if (1 != simpleJdbcTemplate.update(sql, args)) {
			LOGGER.log(Level.WARNING, 
					"Error updating directory stats, dir_id={0}", dir.getDirId());
		}
	}

    /**
     * {@inheritDoc}
     */
    @Override
	public void deleteDir(DbDirectory dir) {
		Map<String, Object> args = ArgUtils.createDirArgs(dir);
		String sqlFiles = "DELETE FROM `ff_files` WHERE :dir_id = `dir_id`";
		String sqlDir = "DELETE FROM `ff_dirs` WHERE :dir_id = `dir_id`";

		simpleJdbcTemplate.update(sqlFiles, args);
		simpleJdbcTemplate.update(sqlDir, args);
	}

    /**
     * {@inheritDoc}
     */
    @Override
    public InsertFileBatch createInsertFileBatch() {
        return new InsertFileBatchImpl(simpleJdbcInsertFile, appDate);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public UpdateFileBatch createUpdateFileBatch() {
        return new UpdateFileBatchImpl(simpleJdbcTemplate);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DeleteFileBatch createDeleteFileBatch() {
        return new DeleteFileBatchImpl(simpleJdbcTemplate);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void updateDir(DbDirectory targetDir, Directory base) {
		String sql = "UPDATE `ff_dirs` " +
                // `dir_flags` = :dir_flags,
			"SET `dir_datetime` = :dir_datetime " +
			"WHERE :dir_id = `dir_id`";

		Map<String, Object> args = Maps.newHashMapWithExpectedSize(3);
		DbDirectory dbDir = (DbDirectory)targetDir;

		args.put("dir_id", dbDir.getDirId());
//		 args.put("dir_flags", base.getDirFlags());
		args.put("dir_datetime", base.getDatetime());

		simpleJdbcTemplate.update(sql, args);
    }
}
